using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Drawing;
using System.IO;
using System.Threading;
using ViewOn.Kernel;
using ViewOn.Helper.IO;
using ViewOn.AddIns;
using ViewOnCore.Helper.Debug;
using ViewOnCore.HostContract;
using ViewOnCore.HostContract.Media;
using ViewOnCore.Device;
using ViewOnCore.HostContract.Tasks;

namespace ViewOn.AddInsHostContract.Media
{

    /// <summary>
    /// Concrete implementation of the media service.
    /// </summary>
    sealed public class MediaService : IMediaService
    {

        #region Variables

        //---- MediaDevices
        private List<MediaDevice> _devices = new List<MediaDevice>();

        private MediaDevice _currentDevice;
        private MediaFeature _currentDeviceFeature;
        private MediaItem _currentDeviceFeatureItem;

        //--- Events
        public event HardwareDeviceEventHandler HardwareDeviceConnected;
        public event HardwareDeviceEventHandler HardwareDeviceDisconnected;

        public event EventHandler MediaDeviceActivation;

        public event MediaDeviceOperationEventHandler MediaDeviceOperation;
        public event MediaItemOperationEventHandler MediaItemOperation;
        public event MediaDataOperationEventHandler MediaDataOperation;

        //---- Medias
        private MediaData _currentMedia = null;
        private MediaPlayStatus _status = MediaPlayStatus.Paused;

        public event EventHandler MediaConnected;
        public event EventHandler MediaCannotConnect;

        //---- Next/Previous management
        private RequestMediaHandler _requestCurrentMedia;
        private RequestMediaHandler _requestNextMedia;
        private RequestMediaHandler _requestPreviousMedia;

        // Events
        public event EventHandler MediaSelectionChanged;
        public event EventHandler PlayerStatusChanged;

        public event EventHandler MediaVisualRefresh;

        //---- Player management
        private bool _isShuffleMode;
        private bool _isRepeatMode;

        //---- Supported files extentions
        private Dictionary<string, string> _musicExtensions = new Dictionary<string, string>();
        private Dictionary<string, string> _radioExtensions = new Dictionary<string, string>();
        private Dictionary<string, string> _videoExtensions = new Dictionary<string, string>();

        //---- Media copy management
        private List<MediaDataCopyHandler> _handlers = new List<MediaDataCopyHandler>();
        private MediaDataCopyHandler _lastHandler;

        #endregion

        #region Constructor

        public MediaService()
        {
            InitializeExtensions();

            //---- Set up options
            IsShuffleMode = Host.SettingsService.IsShuffleMode;
            IsRepeatMode = Host.SettingsService.IsRepeatMode;
        }

        #endregion

        #region InitializeExtensions

        private void InitializeExtensions()
        {
            //---- Music
            string[] ext = { ".mp3", ".mp2", ".mp1", ".ogg", ".wav", ".aif", ".wma", ".aac", ".mp4", ".m4a", ".ac3" };
            foreach (string currentExtension in ext)
                _musicExtensions.Add(currentExtension, currentExtension);

            //---- Radio
            //---- ram : realplayer
            //---- qtl : quicktieme
            //---- ram : realplayer
            string[] extRadios = { ".asx", ".m3u", ".pls" };
            foreach (string currentExtension in extRadios)
                _radioExtensions.Add(currentExtension, currentExtension);

            //---- Video
            string[] extVideos = { ".wmv", ".avi", ".mpg", ".mpeg", ".mov" };
            foreach (string currentExtension in extVideos)
                _videoExtensions.Add(currentExtension, currentExtension);
        }

        #endregion

        #region Pluggable behaviors

        public RequestMediaHandler RequestCurrentMedia
        {
            set
            {
                _requestCurrentMedia = value;
            }
        }

        public RequestMediaHandler RequestNextMedia
        {
            set
            {
                _requestNextMedia = value;
            }
        }

        public RequestMediaHandler RequestPreviousMedia
        {
            set
            {
                _requestPreviousMedia = value;
            }
        }

        #endregion

        #region Properties

        public ReadOnlyCollection<MediaDevice> MediaDevices
        {
            get
            {
                return _devices.AsReadOnly();
            }
        }

        public MediaDevice CurrentMediaDevice
        {
            get
            {
                return _currentDevice;
            }
            set
            {
                _currentDevice = value;
            }
        }

        public MediaFeature CurrentMediaDeviceFeature
        {
            get
            {
                return _currentDeviceFeature;
            }
            set
            {
                _currentDeviceFeature = value;
            }
        }

        public MediaItem CurrentMediaDeviceFeatureItem
        {
            get
            {
                return _currentDeviceFeatureItem;
            }
            set
            {
                _currentDeviceFeatureItem = value;
            }
        }

        public MediaData CurrentMedia
        {
            get
            {
                return _currentMedia;
            }
            set
            {
                _currentMedia = value;
            }
        }

        public TimeSpan TotalTime
        {
            get
            {
                return BassPlayer.TotalTime;
            }
        }

        public bool IsMuted
        {
            get
            {
                return BassPlayer.IsMuted;
            }
        }

        public int Volume
        {
            get
            {
                return BassPlayer.Volume;
            }
            set
            {
                BassPlayer.Volume = value;
            }
        }

        public float Position
        {
            get
            {
                if (_currentMedia == null)
                    return 0;

                return BassPlayer.Position;
            }
            set
            {
                if (_currentMedia == null)
                    return;

                BassPlayer.Position = value;
            }
        }

        public MediaPlayStatus MediaPlayStatus
        {
            get
            {
                return _status;
            }
            set
            {
                _status = value;
            }
        }

        public bool IsShuffleMode
        {
            get
            {
                return _isShuffleMode;
            }
            set
            {
                _isShuffleMode = value;
            }
        }

        public bool IsRepeatMode
        {
            get
            {
                return _isRepeatMode;
            }
            set
            {
                _isRepeatMode = value;
            }
        }

        public Dictionary<string, string> SupportedMusicExtensions
        {
            get
            {
                return _musicExtensions;
            }
        }

        public Dictionary<string, string> SupportedRadioExtensions
        {
            get
            {
                return _radioExtensions;
            }
        }

        public Dictionary<string, string> SupportedVideoExtensions
        {
            get
            {
                return _videoExtensions;
            }
        }

        public int InternalStreamHandle
        {
            get
            {
                return BassPlayer.Stream;
            }
        }

        #endregion

        #region InsertMediaDevice

        public void InsertMediaDevice(int index, MediaDevice device)
        {
            _devices.Insert(index, device);

            device.OnRegistered();

            if (MediaDeviceOperation != null)
                MediaDeviceOperation(MediaOperationKind.Added, device);
        }

        #endregion

        #region AddMediaDevice

        public void AddMediaDevice(MediaDevice device)
        {
            _devices.Add(device);

            device.OnRegistered();

            if (MediaDeviceOperation != null)
                MediaDeviceOperation(MediaOperationKind.Added, device);
        }

        #endregion

        #region RemoveMediaDevice

        public void RemoveMediaDevice(MediaDevice device)
        {
            device.OnSaveChanges();

            _devices.Remove(device);

            device.OnUnregistered();

            if (MediaDeviceOperation != null)
                MediaDeviceOperation(MediaOperationKind.Removed, device);
        }

        #endregion

        #region ActivateMediaDevice

        public void ActivateMediaDevice(MediaDevice newMediaDevice,
                                        MediaFeature newFeature,
                                        MediaItem newFeatureItem)
        {
            //---- Get the previous
            MediaDevice previousMediaDevice = Host.MediaService.CurrentMediaDevice;
            MediaFeature previousFeature = Host.MediaService.CurrentMediaDeviceFeature;
            MediaItem previousFeatureItem = Host.MediaService.CurrentMediaDeviceFeatureItem;

            //---- Need default selection ?
            if (newMediaDevice == null)
                newMediaDevice = Host.MediaService.FindDeviceByHandle("MyLibrary");
            if (newFeature == null)
                newFeature = newMediaDevice.MediaFeatures[0];

            //---- Activation / Deactivation
            if (previousMediaDevice != newMediaDevice)
            {
                if (previousMediaDevice != null)
                    previousMediaDevice.IsActive = false;

                ((MediaService)Host.MediaService).CurrentMediaDevice = newMediaDevice;
                newMediaDevice.IsActive = true;

                Host.MediaService.CurrentMediaDevice.OnLoadOnDemand();
            }            

            if (previousFeature != newFeature)
            {
                if (previousFeature != null)
                    previousFeature.IsActive = false;

                ((MediaService)Host.MediaService).CurrentMediaDeviceFeature = newFeature;
                newFeature.IsActive = true;
                newFeature.OnLoadOnDemand();
            }

            if (previousFeatureItem != newFeatureItem)
            {
                if (previousFeatureItem != null)
                    previousFeatureItem.IsActive = false;

                ((MediaService)Host.MediaService).CurrentMediaDeviceFeatureItem = newFeatureItem;

                if (newFeatureItem != null)
                {
                    newFeatureItem.IsActive = true;
                    newFeatureItem.OnLoadOnDemand();
                }
            }

            //---- Notify
            if (MediaDeviceActivation != null)
                MediaDeviceActivation(null, null);
        }

        #endregion

        #region Next / Previous

        public void NextMedia()
        {
            // Do 20 attempts to find a file
            for (int index = 0; index < 20; index++)
            {
                _currentMedia = _requestNextMedia();
                if (_currentMedia == null)
                    break;
                if (_currentMedia.IsPresent)
                    break;
            }

            // Play
            if (_currentMedia == null)
                Stop();
            else
                PlayMedia(_currentMedia);

            // Notify
            if (MediaSelectionChanged != null)
                MediaSelectionChanged(null, null);
        }

        public void PreviousMedia()
        {
            // Do 20 attempts to find a file
            for (int index = 0; index < 20; index++)
            {
                _currentMedia = _requestPreviousMedia();
                if (_currentMedia == null)
                    break;
                if (_currentMedia.IsPresent)
                    break;
            }

            // Play
            if (_currentMedia == null)
                Stop();
            else
                PlayMedia(_currentMedia);

            // Notify
            if (MediaSelectionChanged != null)
                MediaSelectionChanged(null, null);
        }

        #endregion

        #region Play/Stop/Pause....

        public void Stop()
        {
            _status = MediaPlayStatus.Stopped;
            BassPlayer.Stop();
            if (PlayerStatusChanged != null)
                PlayerStatusChanged(null, null);
        }

        public void Play()
        {
            if (_currentMedia == null)
            {
                if (_status == MediaPlayStatus.Play)
                {
                    _status = MediaPlayStatus.Paused;
                    BassPlayer.Pause();
                }
                else
                {
                    _status = MediaPlayStatus.Play;

                    // Do 20 attempts to find a file
                    for (int index = 0; index < 20; index++)
                    {
                        _currentMedia = _requestCurrentMedia();
                        if (_currentMedia == null)
                            break;
                        if (_currentMedia.IsPresent)
                            break;
                    }

                    if (_currentMedia != null)
                        PlayMedia(_currentMedia);

                    // Notify
                    if (MediaSelectionChanged != null)
                        MediaSelectionChanged(null, null);
                }

                if (PlayerStatusChanged != null)
                    PlayerStatusChanged(null, null);

                return;
            }

            //---- Normal behavior
            if (_status == MediaPlayStatus.Play)
            {
                _status = MediaPlayStatus.Paused;
                BassPlayer.Pause();
            }
            else
            {
                _status = MediaPlayStatus.Play;
                BassPlayer.Play();
            }

            if (PlayerStatusChanged != null)
                PlayerStatusChanged(null, null);
        }

        public void PlayMedia(MediaData mediaData)
        {
            Stop();

            _status = MediaPlayStatus.Play;

            bool isAnotherMediaFile = _currentMedia == null || !_currentMedia.Equals(mediaData);
            _currentMedia = mediaData;

            Host.UIService.ApplicationTitle = mediaData.ShortTitle;

            //---- Play
            // If there are other tasks, cancel theses tasks and then simply play
            // If there are no task, use the normal task queue
            // NB: Like this the "spinner" is displayed during connectiont to the stream
            PlayMediaKernelTask task = new PlayMediaKernelTask(mediaData);
            Host.EnqueueUniqueTask(new PlayMediaKernelTask(mediaData));

            //---- Notify
            if (PlayerStatusChanged != null)
                PlayerStatusChanged(null, null);

            if (isAnotherMediaFile)
                if (MediaSelectionChanged != null)
                    MediaSelectionChanged(null, null);
        }

        public void MuteInverse()
        {
            BassPlayer.MuteInverse();
        }

        public void ShuffleInverse()
        {
            IsShuffleMode = !IsShuffleMode;
            Host.SettingsService.IsShuffleMode = IsShuffleMode;
        }

        public void RepeatInverse()
        {
            IsRepeatMode = !IsRepeatMode;
            Host.SettingsService.IsRepeatMode = IsRepeatMode;
        }

        #endregion

        #region NotifyMediaItemOperation

        public void NotifyMediaItemOperation(MediaFeature feature, MediaOperationKind operation, MediaItem featureItem)
        {
            if (MediaItemOperation != null)
                MediaItemOperation(feature, operation, featureItem);
        }

        #endregion

        #region NotifyMediaDataOperation

        public void NotifyMediaDataOperation(MediaFeature feature, MediaItem featureItem, MediaOperationKind operation, List<MediaData> medias)
        {
            if (MediaDataOperation != null)
                MediaDataOperation(feature, featureItem, operation, medias);
        }

        #endregion

        #region NotifyMediaVisualRefresh

        public void NotifyMediaVisualRefresh()
        {
            if (MediaVisualRefresh != null)
                MediaVisualRefresh(null, null);
        }

        #endregion

        #region Media copy management : RegisterCopyHandler

        public void RegisterCopyHandler(MediaDataCopyHandler handler)
        {
            _handlers.Add(handler);
        }

        #endregion

        #region Media copy management : CanCopyMediaDatas / CanCopyMediaDatasToDisk

        public bool CanCopyMediaDatas(MediaFeature feature, MediaItem featureItem)
        {
            // Search for a handler able to handle it
            foreach (MediaDataCopyHandler handler in _handlers)
                if (handler.CanCopy(feature, featureItem))
                {
                    _lastHandler = handler;
                    return true;
                }

            return false;
        }

        public bool CanCopyMediaDatasToDisk()
        {
            // Search for a handler able to handle it
            foreach (MediaDataCopyHandler handler in _handlers)
                if (handler.CanCopyToDisk())
                {
                    _lastHandler = handler;
                    return true;
                }

            return false;
        }

        #endregion

        #region Media copy management : CopyMediaDatas / CopyMediaDatasToDisk

        public void CopyMediaDatas(List<MediaData> mediasToCopy, MediaFeature feature, MediaItem featureItem)
        {
            // Search for a handler able to handle it
            _lastHandler.Copy(mediasToCopy, feature, featureItem);
        }

        public void CopyMediaDatasToDisk(List<MediaData> mediasToCopy, string path)
        {
            if (!path.EndsWith("/") && !path.EndsWith("\\"))
                path += "\\";

            // Search for a handler able to handle it
            _lastHandler.CopyToDisk(mediasToCopy, path);
        }

        #endregion

        #region Media copy management : ImportMediaDatas

        public void ImportMediaDatas(MediaFolder parentFolder, string path)
        {
            Task task = CurrentMediaDeviceFeature.GetImportDirectoryTask(parentFolder, path);
            Host.EnqueueTask(task);
        }

        #endregion

        #region CRUD : RemoveMediaDatas

        public void RemoveMediaDatas(List<MediaData> mediaDatas, MediaFeature mediaDeviceFeature, MediaItem mediaDeviceItem, MediaFolder parentFolder)
        {
            //---- Remove from device feature
            if (mediaDeviceItem == null)
            {
                if (mediaDeviceFeature.IsRemoveSupported)
                {
                    // Stop this media
                    if (Host.MediaService.CurrentMedia != null && mediaDatas.Contains(Host.MediaService.CurrentMedia))
                        Host.MediaService.Stop();

                    // Remove	
                    mediaDeviceFeature.RemoveMediaDatas(parentFolder, mediaDatas);
                    foreach (MediaItem item in mediaDeviceFeature.MediaItems)
                        item.RemoveMediaDatas(mediaDatas);

                    // Notify
                    Host.MediaService.NotifyMediaDataOperation(mediaDeviceFeature, mediaDeviceItem, MediaOperationKind.Removed, mediaDatas);
                }
            }

            //---- Remove from device feature item
            else
            {
                if (mediaDeviceItem.IsRemoveSupported)
                {
                    mediaDeviceItem.RemoveMediaDatas(mediaDatas);

                    // Notify
                    Host.MediaService.NotifyMediaDataOperation(mediaDeviceFeature, mediaDeviceItem, MediaOperationKind.Removed, mediaDatas);
                }
            }
        }

        #endregion

        #region CRUD : UpdateMediaDatas

        public void UpdateMediaDatas(List<MediaData> mediaDatas, MediaFeature mediaDeviceFeature, MediaDataTagField field, object newValue)
        {
            if (!mediaDeviceFeature.IsTagUpdateSupported)
                return;

            UpdateMediaDataTagTask task = new UpdateMediaDataTagTask(mediaDatas, Host.MediaService.CurrentMediaDeviceFeature, field, newValue);
            Host.EnqueueTask(task);
        }

        #endregion

        #region Media files associations

        static private string ProgId = "ViewOnTv";

        public void SetMediaFileAssociation(string extension)
        {
            try
            {
                FileAssociationInfo fai = new FileAssociationInfo(extension);
                fai.Create(ProgId);
                fai.ContentType = "application/myfile";
                fai.OpenWithList = new string[] { "viewon.exe" };
                //fai.Vista(ProgId, "viewon.exe", shortKey);
            }
            catch (Exception e)
            {
                LogManager.Handle(e);
            }
        }

        public bool IsMediaFileAssociated(string extension)
        {
            FileAssociationInfo fai = new FileAssociationInfo(extension);
            return (fai.ProgID == ProgId);
        }

        #endregion

        #region Tasks : CreateTask

        public Task CreateTask(MediaServiceTasks taskType, params object[] parameters)
        {
            Task task = null;

            //---- Rip Music CD
            if (taskType == MediaServiceTasks.RipMusicCD)
            {
                if (parameters.Length == 3)
                    task = new RipMusicCDTask((MediaFeature)parameters[0],
                                                (MediaItem)parameters[1],
                                                (List<MediaData>)parameters[2]);
                else if (parameters.Length == 2)
                    task = new RipMusicCDTask((string)parameters[0],
                                            (List<MediaData>)parameters[1]);
            }

            return task;
        }

        #endregion

        #region IsSupportedMusicFile

        public bool IsSupportedMusicFile(string filePath)
        {
            // File formats extentions : http://jaybrain.sourceforge.net/normal/index.html
            return Host.MediaService.SupportedMusicExtensions.ContainsKey(GetExtension(filePath));
        }

        #endregion

        #region IsSupportedRadioFile

        public bool IsSupportedRadioFile(string filePath)
        {
            // File formats extentions : http://jaybrain.sourceforge.net/normal/index.html
            return Host.MediaService.SupportedRadioExtensions.ContainsKey(GetExtension(filePath));
        }

        #endregion

        #region IsSupportedVideoFile

        public bool IsSupportedVideoFile(string filePath)
        {
            return Host.MediaService.SupportedVideoExtensions.ContainsKey(GetExtension(filePath));
        }

        #endregion

        #region OnMediaEnded

        internal void OnMediaEnded()
        {
            NextMedia();
        }

        #endregion

        #region OnMediasOperation

        public void OnMediasOperation(MediaFeature feature, MediaItem featureItem, MediaOperationKind operation, List<MediaData> medias)
        {
            if (MediaDataOperation != null)
                MediaDataOperation(feature, featureItem, operation, medias);
        }

        #endregion

        #region OnMediaDataConnected

        internal void OnMediaDataConnected()
        {
            if (MediaConnected != null)
                MediaConnected(null, null);
        }

        #endregion

        #region OnMediaDataCannotConnect

        internal void OnMediaDataCannotConnect()
        {
            if (MediaCannotConnect != null)
                MediaCannotConnect(null, null);
        }

        #endregion

        #region OnHardwareDeviceConnected / OnHardwareDeviceDisconnected

        public void OnHardwareDeviceConnected(string drive)
        {
            if (HardwareDeviceConnected != null)
                HardwareDeviceConnected(drive);
        }

        public void OnHardwareDeviceDisconnected(string drive)
        {
            // Check if we can continue to play the current media ?
            if (Host.MediaService.CurrentMediaDevice.DriveLetter == drive)
                Host.MediaService.Stop();

            if (HardwareDeviceDisconnected != null)
                HardwareDeviceDisconnected(drive);
        }

        #endregion

        #region Helpers : Find...

        public MediaDevice FindDeviceByHandle(string handle)
        {
            foreach (MediaDevice device in Host.MediaService.MediaDevices)
                if (device.Handle == handle)
                    return device;

            return null;
        }

        public MediaFeature FindDeviceFeatureByHandle(string handle)
        {
            foreach (MediaDevice device in Host.MediaService.MediaDevices)
                foreach (MediaFeature feature in device.MediaFeatures)
                    if (feature.Handle == handle)
                        return feature;

            return null;
        }

        #endregion

        #region Helpers : Tag...

        /// <summary>
        /// Extract a bitmap from a file.
        /// </summary>
        /// <param name="filePath">The file path</param>
        /// <returns>The extracted bitmap</returns>
        public Bitmap TagExtractBitmap(string filePath)
        {
            try
            {
                TagLib.File tagFile = TagLib.File.Create(filePath);
                if (tagFile.Tag.Pictures.Length > 0)
                    if (tagFile.Tag.Pictures[0].Type == TagLib.PictureType.FrontCover)
                    {
                        MemoryStream stream = new MemoryStream(tagFile.Tag.Pictures[0].Data.Data);
                        return new Bitmap(stream);
                    }
            }
            catch (Exception e)
            {
                LogManager.Handle(e);
            }

            return null;
        }

        /// <summary>
        /// Create a picture information from the bitmap.
        /// </summary>
        /// <param name="bitmap">The bitmap to analyse</param>
        /// <returns>Returns an object containing the information.</returns>
        public object TagCreatePictureInformation(Bitmap bitmap)
        {
            try
            {
                MemoryStream stream = new MemoryStream();
                bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
                stream.Flush();
                stream.Seek(0, SeekOrigin.Begin);

                return new TagLib.Picture(TagLib.ByteVector.FromStream(stream));
            }
            catch (Exception e)
            {
                LogManager.Handle(e);
                return null;
            }
        }

        /// <summary>
        /// Save the picture to file from its information object.
        /// </summary>
        /// <param name="pictureInformation">The object containing the information</param>
        /// <param name="filePath">The path to the target file</param>
        public void TagSaveImageInformation(object pictureInformation, string filePath)
        {
            try
            {
                TagLib.File file = TagLib.Mpeg.File.Create(filePath);
                TagLib.Picture[] pictures = new TagLib.Picture[1];
                pictures[0] = (TagLib.Picture)pictureInformation;
                TagLib.Id3v2.Tag id3v2 = (TagLib.Id3v2.Tag)file.GetTag(TagLib.TagTypes.Id3v2);
                if (id3v2 != null)
                    id3v2.Pictures = pictures;
                else
                {
                    TagLib.Mpeg4.AppleTag apple = (TagLib.Mpeg4.AppleTag)file.GetTag(TagLib.TagTypes.Apple);
                    if (apple != null)
                        apple.Pictures = pictures;
                }

                file.Save();
            }
            catch (Exception e)
            {
                LogManager.Handle(e);
            }
        }

        #endregion

        #region Helper : misc...

        public long GetFileTotalTime(string path)
        {
            return (long)BassPlayer.GetFileTotalTime(path).TotalSeconds;
        }

        private string GetExtension(string filePath)
        {
            // Throw some exception is some 'special cases'.
            // Path.GetExtension(filePath).ToLower();

            int lastIndex = filePath.LastIndexOf('.');
            if (lastIndex < 0)
                return "";

            return filePath.Substring(lastIndex).ToLower();
        }

        #endregion

    }

}
